//------------------------------------------------------------------------------
// <copyright file="FilterQuery.cs" company="Microsoft">
//     
//      Copyright (c) 2006 Microsoft Corporation.  All rights reserved.
//     
//      The use and distribution terms for this software are contained in the file
//      named license.txt, which can be found in the root of this distribution.
//      By using this software in any fashion, you are agreeing to be bound by the
//      terms of this license.
//     
//      You must not remove this notice, or any other, from this software.
//     
// </copyright>                                                                
//------------------------------------------------------------------------------

namespace MS.Internal.Xml.XPath {
    using System;
    using System.Xml;
    using System.Xml.XPath;
    using System.Diagnostics;
    using System.Globalization;
    using System.Xml.Xsl;

    internal sealed class FilterQuery : BaseAxisQuery {
        private Query cond;
        private bool noPosition;
        
        public FilterQuery(Query qyParent, Query cond, bool noPosition) : base(qyParent) {
            this.cond = cond;
            this.noPosition = noPosition;
        }
        private FilterQuery(FilterQuery other) : base(other) {
            this.cond       = Clone(other.cond);
            this.noPosition = other.noPosition;
        }        

        public override void Reset() {
            cond.Reset();
            base.Reset();
        }

        public Query Condition { get { return cond; } }

        public override void SetXsltContext(XsltContext input) {
            base.SetXsltContext(input);
            cond.SetXsltContext(input);
            if (cond.StaticType != XPathResultType.Number && cond.StaticType != XPathResultType.Any && noPosition) {
                ReversePositionQuery query = qyInput as ReversePositionQuery;
                if (query != null) {
                    qyInput = query.input;
                }
            }
        }

        public override XPathNavigator Advance() {
            while ((currentNode = qyInput.Advance()) != null) {
                if (EvaluatePredicate()) {
                    position++;
                    return currentNode;
                }
            }
            return null;
        }

        internal bool EvaluatePredicate() {
            object value = cond.Evaluate(qyInput);
            if (value is XPathNodeIterator) return cond.Advance() != null;
            if (value is string           ) return ((string)value).Length != 0;
            if (value is double           ) return (((double)value) == qyInput.CurrentPosition);
            if (value is bool             ) return (bool)value;
            Debug.Assert(value is XPathNavigator, "Unknown value type");
            return true;
        }

        public override XPathNavigator MatchNode(XPathNavigator current) {
            XPathNavigator context;
            if (current == null) {
                return null;
            }
            context = qyInput.MatchNode(current);

            if (context != null) {
                // In this switch we process some special case in wich we can calculate predicate faster then in generic case
                switch (cond.StaticType) {
                case XPathResultType.Number:
                    OperandQuery operand = cond as OperandQuery;
                    if (operand != null) {
                        double val = (double)operand.val;
                        ChildrenQuery childrenQuery = qyInput as ChildrenQuery;
                        if (childrenQuery != null) { // foo[2], but not foo[expr][2]
                            XPathNavigator result = current.Clone();
                            result.MoveToParent();
                            int i = 0;
                            result.MoveToFirstChild();
                            do {
                                if (childrenQuery.matches(result)) {
                                    i++;
                                    if (current.IsSamePosition(result)) {
                                        return val == i ? context : null;
                                    }
                                }
                            } while (result.MoveToNext());
                            return null;
                        }
                        AttributeQuery attributeQuery = qyInput as AttributeQuery;
                        if (attributeQuery != null) {// @foo[3], but not @foo[expr][2]
                            XPathNavigator result = current.Clone();
                            result.MoveToParent();
                            int i = 0;
                            result.MoveToFirstAttribute();
                            do {
                                if (attributeQuery.matches(result)) {
                                    i++;
                                    if (current.IsSamePosition(result)) {
                                        return val == i ? context : null;
                                    }
                                }
                            } while (result.MoveToNextAttribute());
                            return null;
                        }
                    }
                    break;
                case XPathResultType.NodeSet:
                    cond.Evaluate(new XPathSingletonIterator(current, /*moved:*/true));
                    return (cond.Advance() != null) ? context : null;
                case XPathResultType.Boolean:
                    if (noPosition) {
                        return ((bool)cond.Evaluate(new XPathSingletonIterator(current, /*moved:*/true))) ? context : null;
                    }
                    break;
                case XPathResultType.String:
                    if (noPosition) {
                        return (((string)cond.Evaluate(new XPathSingletonIterator(current, /*moved:*/true))).Length != 0) ? context : null;
                    }
                    break;
                case XPathResultType_Navigator:
                    return context;
                default:
                    return null;
                }
                /* Generic case */ {
                    Evaluate(new XPathSingletonIterator(context, /*moved:*/true));
                    XPathNavigator result;
                    while ((result = Advance()) != null) {
                        if (result.IsSamePosition(current)) {
                            return context;
                        }
                    }
                }
            }
            return null;
        }

        public override QueryProps Properties { 
            get { 
                return QueryProps.Position | (qyInput.Properties & (QueryProps.Merge | QueryProps.Reverse)); 
            } 
        }

        public override XPathNodeIterator Clone() { return new FilterQuery(this); }

        public override void PrintQuery(XmlWriter w) {
            w.WriteStartElement(this.GetType().Name);
            if (! noPosition
                ) {
                w.WriteAttributeString("position", "yes");
            }
            qyInput.PrintQuery(w);
            cond.PrintQuery(w);
            w.WriteEndElement();
        }
    }
}
